/***********************************************************************

	This file is part of KEEL-software, the Data Mining tool for regression, 
	classification, clustering, pattern mining and so on.

	Copyright (C) 2004-2010
	
	F. Herrera (herrera@decsai.ugr.es)
    L. Sánchez (luciano@uniovi.es)
    J. Alcalá-Fdez (jalcala@decsai.ugr.es)
    S. García (sglopez@ujaen.es)
    A. Fernández (alberto.fernandez@ujaen.es)
    J. Luengo (julianlm@decsai.ugr.es)

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see http://www.gnu.org/licenses/
  
**********************************************************************/

package keel.Algorithms.Decision_Trees.SLIQ;
import java.util.*;

import keel.Dataset.*;


/** 
 * Class to implement the dataset
 */

public class Dataset {
	/** The name of the dataset. */
	protected String name = "";
	
	/** The attributes. */
	protected Vector attributes;
	
	/** The itemsets. */
	protected Vector itemsets;	
		
	/** The index of the class attribute. */
	protected int classIndex;
	
	/** Keel dataset InstanceSet **/
	protected InstanceSet IS;

	/** Function to read the .dat file that contains the information of the dataset.
     *
     * @param name 		The reader object where the itemsets are readed.
     * @param train     The flag if the file is for training
     */
  	public Dataset(String name, boolean train) {
 		try {
            // Crea el conjunto de instancias
            IS = new InstanceSet();
            // Lee los conjuntos de elementos.
            IS.readSet(name,train);
		} catch(DatasetException e) {
			System.out.println("Error al leer instancias del dataset");
			e.printStackTrace();
			System.exit(-1);
		} catch(HeaderFormatException e) {
			System.out.println("Error al leer instancias del dataset");
			e.printStackTrace();
			System.exit(-1);
		}
		
		// Almacenar los atributos del dataset
		readHeader();

  		itemsets = new Vector(IS.getNumInstances());
  		
  		// Leer todos los conjuntos de elementos
  		getItemsetFull();  		  		
	}
 
	/** Constructor that copies another dataset.
     *
     * @param dataset		The dataset to be copied.
     */
  	public Dataset(Dataset dataset) {
  		this(dataset, dataset.numItemsets());
  		dataset.copyItemsets(0, this, dataset.numItemsets());
	}
  	
  	
  	/** Constructor to copy all the attributes of another dataset but the itemsets.
     *
     * @param dataset		The dataset to be copied.
     * @param capacity		The number of itemsets.
     */
  	public Dataset(Dataset dataset, int capacity)  {
  		if(capacity < 0) 
  			capacity = 0;
    
  		classIndex = dataset.classIndex;
  		name = dataset.getName();
  		attributes = dataset.attributes;
  		itemsets = new Vector(capacity);
	}

  	/** Function to stores header of a data file.
     *
     */
  	private void readHeader()  {
  		String attributeName;
  		Vector attributeValues;
  		int i;

		name = Attributes.getRelationName();
		
  		// Vectores para almacenar temporalmente la informacion.
  		attributes = new Vector();
 
  		keel.Dataset.Attribute at;
  		
  		// almacenar atributo, entradas y salidas de la cabecera
  		for (int j=0; j<Attributes.getNumAttributes(); j++) {
  			at=Attributes.getAttribute(j);
  			attributeName = at.getName();
  			
  			// Comprobar si es de tipo real
  			if(at.getType()==2) {
  			 float min = (float) at.getMinAttribute();
  			 float max = (float) at.getMinAttribute();
  			 attributes.addElement(new Attribute(attributeName, j));
 			 Attribute att = (Attribute)attributes.elementAt(j);
			 att.setRange(min, max);  	
			 att.activate();
  			} else {
             // Comprobar si el tipo es entero
  			 if(at.getType()==1) {	
  			 	int min = (int) at.getMinAttribute();
  			 	int max = (int) at.getMinAttribute();  			 	  			 	  			 	
  			 	attributes.addElement(new Attribute(attributeName, j));
  			 	Attribute att = (Attribute)attributes.elementAt(j);
  			 	att.setRange(min, max);  	
  			 	att.activate();
  			 } else { // es discreto
  			 	attributeValues = new Vector();
  			 	for(int k=0; k<at.getNumNominalValues();k++) {
  			 		attributeValues.addElement(at.getNominalValue(k));
  			 	}
  			 	attributes.addElement(new Attribute(attributeName, attributeValues, j));
  			 	Attribute att = (Attribute)attributes.elementAt(j);
  			 	att.activate();
  			 }  			 	
  			}  			 		
  		} // for
  			
  		// Establecer el indice de la clase de salida
  		classIndex = Attributes.getNumAttributes() - 1;  				
	}

  	
  	
  	/** Function to read an itemset and appends it to the dataset.
     *
     *
     * @return					True if the itemset was readed succesfully.
     *
     */
  	private boolean getItemsetFull()  {
   		// rellenar el conjunto de elementos
  		for(int j=0; j<IS.getNumInstances(); j++) {
  	  		double[] itemset = new double[Attributes.getNumAttributes()];

  			// Obtener valores para todos los atributos de entrada.
  			for(int i=0; i<Attributes.getInputNumAttributes(); i++) {
  				// comprobar el tipo y si es nulo
  				if(IS.getInstance(j).getInputMissingValues(i))
  					itemset[i] = Itemset.getMissingValue();
  				else {
  					if(Attributes.getInputAttribute(i).getType()==0) { // nominal
  						for(int k=0; k<Attributes.getAttribute(i).getNumNominalValues();k++)	
  							if(Attributes.getAttribute(i).getNominalValue(k).equals(IS.getInstance(j).getInputNominalValues(i)))
  								itemset[i]=(double)k;	
  					} else { // real y entero
  						itemset[i]=IS.getInstance(j).getInputRealValues(i);  				    				
  					}
  				} // else  				            
			} //for
  	
  			// Obtener valores para el atributo de salida.
  			int i=Attributes.getInputNumAttributes();
  			
  			// comprobar el tipo y si es nulo
            if(IS.getInstance(j).getOutputMissingValues(0))
                itemset[i] = Itemset.getMissingValue();
            else {
                if(Attributes.getOutputAttribute(0).getType()==0) { //nominal
                    for(int k=0; k<Attributes.getOutputAttribute(0).getNumNominalValues();k++)
                        if(Attributes.getOutputAttribute(0).getNominalValue(k).equals(IS.getInstance(j).getOutputNominalValues(0)))
                            itemset[i]=(double)k;
                } else { // real y entero
                    itemset[i]=IS.getInstance(j).getOutputRealValues(0);
                }
            } // else

            // Agregar el conjunto de elementos al dataset
            addItemset(new Itemset(1, itemset));
  			
		} // for
  		return true;
	}

	/** Function to add one itemset.
     *
     * @param itemset	The itemset to add to the dataset.
     */
  	public final void addItemset(Itemset itemset) {
  		Itemset newItemset = (Itemset)itemset.copy();
 		
  		newItemset.setDataset(this);
  		itemsets.addElement(newItemset);  		  	  		
	}

  	/** Returns the name of the dataset.
     *
     * @return the name of the dataset.
     */
  	public String getName() {
  		return name;
  	}

  	/** Returns the attribute that has the index.
     *
     * @param index		The index of the attribute.
     * @return the attribute that has the index.
     */
	public final Attribute getAttribute(int index) {
  		return (Attribute) attributes.elementAt(index);
	}

  	/** Returns the attribute that has the name.
     *
     * @param name	The name of the attribute.
     * @return the attribute that has the name.
     */
	public final Attribute getAttribute(String name) {
  		for(int i=0; i<attributes.size(); i++)
  			if(((Attribute)attributes.elementAt(i)).name().equalsIgnoreCase(name)) 
  				return (Attribute) attributes.elementAt(i);
  			
  		return null;
	}

	/** Returns class attribute.
     *
     * @return class attribute.
     */
	public final Attribute getClassAttribute() {
  		if(classIndex < 0) {
  			System.err.println("indice de clase incorrecto:"+classIndex);
  			return null;
  		}
  		return getAttribute(classIndex);
	}

	/** Returns the index of the class attribute.
     *
     * @return the index of the class attribute.
     */
	public final int getClassIndex() {
  		return classIndex;
	}
 
	/** Returns the number of attributes.
     *
     * @return the number of attributes.
     */
	public final int numAttributes() {
  		return attributes.size();
	}

	/** Returns the number of possible values of the class attribute.
     *
     * @return the number of possible values of the class attribute.
     */
	public final int numClasses() {
  		if (classIndex < 0) {
  			System.err.println("indice de clase erroneo:"+classIndex);
  			return -1;
  		}
   		return getClassAttribute().numValues();
	}

	/** Returns the number of itemsets.
     *
     * @return the number of itemsets.
     */
	public final int numItemsets() {
  		return itemsets.size();
	}

  	/** Function to remove an itemset at the given position.
     *
     * @param index 	The index of the itemset to be deleted.
     */
  	public final void delete(int index) {
  		itemsets.removeElementAt(index);
	}

	 /** Function to remove all the attributes with missing value in the given attribute.
     *
     * @param attIndex		The index of the attribute.
     */
  	public final void deleteWithMissing(int attIndex) {
  		Vector newItemsets = new Vector(numItemsets());

  		for(int i=0; i<numItemsets(); i++)
  			if(!itemset(i).isMissing(attIndex))
  				newItemsets.addElement(itemset(i));

  		itemsets = newItemsets;
	}

	/** Enumerates all the attributes.
     *
     * @return An enumeration that contains all the attributes.
     */
	public Enumeration enumerateAttributes()  {
  		Vector help = new Vector(attributes.size() - 1);
  		
  		for(int i=0; i<attributes.size(); i++)
  			if(i != classIndex)
  				help.addElement(attributes.elementAt(i));
  		
  		return help.elements();
	}

	/** Enumerates all the itemsets.
     *
     * @return	An enumeration that contains all the itemsets.
     */
	public final Enumeration enumerateItemsets() {
  		return itemsets.elements();
	}

	/** Returns the itemset at the given position.
     *
     * @param index	The index of the itemset.
     * @return the itemset at the given position.
     */
	public final Itemset itemset(int index) {
  		return (Itemset)itemsets.elementAt(index);
	}

	/** Returns the last itemset.
     *
     * @return the last itemset.
     */
	public final Itemset lastItemset() {
  		return (Itemset)itemsets.lastElement();
	}

	
  	/** Function to add  the instances of one set to the end of another.
     *
     * @param from	The index of the first that is going to be copied.
     * @param dest	The dataset where the itemsets are going to be copied.
     * @param num	The number of itemsets to copy.
     */
  	private void copyItemsets(int from, Dataset dest, int num) {
	    for(int i=0; i<num; i++)
	      dest.addItemset(itemset(from + i));
    }

  	/** Function to compute the sum of all the weights of the itemsets.
     *
     * @return	The weight of all the itemsets.
     */
  	public final double sumOfWeights() {
  		double sum = 0;

  		for(int i=0; i<numItemsets(); i++)
  			sum += itemset( i ).getWeight();

  		return sum;
	}

  	/** Function to sort the dataset based on an attribute.
     *
     * @param attIndex	The index of the attribute.
     */
  	public final void sort(int attIndex)  {
  		int i, j;

  		// Llevar al final todo el dataset con datos ausentes
  		j = numItemsets() - 1;
  		i = 0;
    
  		while(i <= j) {
  			if(itemset(j).isMissing(attIndex))
  				j--;
  			else {
  				if(itemset(i).isMissing(attIndex))  {
  					swap(i, j);
  					j--;
  				}
  				i++;
  			}
  		}
    
  		quickSort(attIndex, 0, j);
	}

  	/** Function to implementate the quicksort method.
     *
     * @param attIndex		The index of the attribute used to sort the itemsets.
     * @param lo0			Minimum value.
     * @param hi0			Maximum value.
     */
  	private void quickSort(int attIndex, int lo0, int hi0) {
  		int lo = lo0, hi = hi0;
  		double mid, midPlus, midMinus;
    
  		if (hi0 > lo0) {
  			// Arbitrarily establishing partition element as the 
  			// midpoint of the array.
  			mid = itemset((lo0 + hi0 ) / 2).getValue(attIndex);
  			midPlus = mid + 1e-6;
  			midMinus = mid - 1e-6;

  			// loop through the array until indices cross
  			while(lo <= hi) {
  				// find the first element that is greater than or equal to 
  				// the partition element starting from the left Index.
  				while((itemset(lo).getValue(attIndex) < midMinus) && (lo < hi0))
  					++lo;
	
  				// find an element that is smaller than or equal to
  				// the partition element starting from the right Index.
  				while ((itemset(hi).getValue(attIndex)  > midPlus) && (hi > lo0))
  					--hi;

  				// if the indexes have not crossed, swap
  				if(lo <= hi) {
  					swap( lo,hi );
  					++lo;
  					--hi;
  				}
  			}

  			// If the right index has not reached the left side of array
  			// must now sort the left partition.
  			if(lo0 < hi)
  				quickSort(attIndex, lo0, hi);

  			// If the left index has not reached the right side of array
  			// must now sort the right partition.
  			if(lo < hi0)
  				quickSort(attIndex, lo, hi0);
  		}
	}

  	/** Function to swap two itemsets.
     *
     * @param i		The first itemset.
     * @param j		The second itemset.
     */
  	private void swap(int i, int j) {
        Object help = itemsets.elementAt( i );

        itemsets.insertElementAt(itemsets.elementAt(j), i);
        itemsets.removeElementAt(i + 1);
        itemsets.insertElementAt(help, j);
        itemsets.removeElementAt(j + 1);
	}
  	


}
